#include <kaleidoscope/frontend/semantic_analysis/semantic_analysis_pass.hpp>
//=====================================================================
#include <boost/spirit/home/phoenix/core.hpp>
#include <boost/spirit/home/phoenix/operator.hpp>
#include <boost/spirit/home/phoenix/bind.hpp> 
#include <boost/bind.hpp>
//=====================================================================
#include <kaleidoscope/common/symbol_ids.hpp>
#include <kaleidoscope/common/decorate.hpp>
#include <kaleidoscope/frontend/syntactical_analysis/marshall.hpp>
#include <kaleidoscope/frontend/syntactical_analysis/relex.hpp>
#include <kaleidoscope/frontend/semantic_analysis/resolver.hpp>
//=====================================================================
using namespace boost::phoenix;
using namespace boost::phoenix::arg_names;
using namespace atma::sooty;
using namespace kal;
using namespace kal::semantic_analysis;
using namespace kal::semantic_analysis::detail;
//=====================================================================






void kal::semantic_analysis::detail::start_rule(int& errors, atma::sooty::const_parseme_ptr_ref N)
{
	for (parseme_container::const_iterator i = N->children.begin(); i != N->children.end(); ++i)
	{
		if ((*i)->id == ids::function)
		{
			// check for multiple function declarations
			//parseme_container others = scope::function::find(LS, *i, kal::resolve::closeness::perfect);
			parseme_container others;
			scope::upwards_copy_if(*i, std::back_inserter(others), scope::other_functions_matching(*i));
			
			// SERIOUSLY: this may be good or bad, I'm not sure:
			// - because we remove from ourselves to the end, the first encountered
			//   function of this symbol won't cause errors
			//const parseme_container::value_type& to_find = *i;
			&parseme::file_position;
			
			// erase all function declarations that occur after us
			others.erase(
				std::remove_if(others.begin(), others.end(), arg1->*&parseme::file_position > (*i)->file_position),
				others.end()
				);
			
			if (!others.empty())
				std::cout
					<< "ERROR ("
					<< relex::file_position(*i).row
					<< "): function '" 
					<< relex::function(*i)
					<< "' already exists"
					<< std::endl;
			
			function(errors, *i);
		}
	}
}


void kal::semantic_analysis::detail::function(int& errors, atma::sooty::const_parseme_ptr_ref N)
{
	// at the moment, we don't really do much else!
	parseme_ptr_ref body = marshall::function::body(N);
	function_body(errors, body);
}


void kal::semantic_analysis::detail::function_body(int& errors, atma::sooty::const_parseme_ptr_ref N)
{
	for (parseme_container::iterator i = N->children.begin(); i != N->children.end(); ++i)
	{
		if ( (*i)->id == ids::return_statement )
			return_statement(errors, *i);
		else if ( (*i)->id == ids::function_call )
			function_call(errors, *i);
	}
}


void semantic_analysis::detail::return_statement(int& errors, atma::sooty::const_parseme_ptr_ref N)
{
	const_parseme_ptr_ref expr = marshall::return_statement::expression(N);

	if (expr->id == ids::function_call)
		function_call(errors, expr);
	else if (expr->id == ids::identifier)
	{
		//resolver resolver(value_table(), type_table());
		//parseme_ptr perhaps = resolver.value(expr);
	}
}


void semantic_analysis::detail::function_call(int& errors, atma::sooty::const_parseme_ptr_ref N)
{
	// find all functions that match our function call both in name and argument types
	parseme_container possibilities;
	scope::upwards_copy_if(N, std::back_inserter(possibilities), scope::function_matches_call(N));
	
	// if we have no candidates, then we've got bad parameters
	// if we have too many, then things are ambiguous
	if (possibilities.empty())
	{
		std::cout 
			<< "ERROR: function call '" 
			<< relex::function_call(N) //marshall::name(N)->value.string
			<< "' has no matching definition"
			<< std::endl;
		
		++errors;
	}
	else if (possibilities.size() >  1)
	{
		std::cout
			<< "ERROR: function call '" 
			<< relex::function_call(N)
			<< "' is ambiguous"
			<< std::endl;
		
		++errors;
	}
	
}

























/*
void kal::semantic_analysis_pass::start_rule(atma::sooty::const_parseme_ptr_ref node)
{
	for (parseme_container::const_iterator i = node->children.begin(); i != node->children.end(); ++i)
	{
		if ((*i)->id == ids::function)
		{
			//const_parseme_ptr_ref function_name = marshall::function::name(*i);
			//if (value_table().lookup(function_name->value.string) != symbol_table::not_found)
			//{
			//	std::cout
			//		<< "Error: Function '"
			//		<< function_name->value.string
			//		<< "' already declared"
			//		<< std::endl;
			//}
			
			function(*i);
		}
	}
}

void kal::semantic_analysis_pass::function(atma::sooty::const_parseme_ptr_ref node)
{
	parseme_ptr_ref body = marshall::function::body(node);
	
	value_table().enter_scope( marshall::function::name(node)->value.string );
	function_body(body);
	value_table().leave_scope();
}

void kal::semantic_analysis_pass::function_body(atma::sooty::const_parseme_ptr_ref body)
{
	for (parseme_container::iterator i = body->children.begin(); i != body->children.end(); ++i)
	{
		if ( (*i)->id == ids::return_statement )
			return_statement(*i);
		else if ( (*i)->id == ids::function_call )
			function_call(*i);
	}
}

void semantic_analysis_pass::return_statement(const_parseme_ptr_ref stmnt)
{
	const_parseme_ptr_ref expr = marshall::return_statement::expression(stmnt);
	
	if (expr->id == ids::function_call)
		function_call(expr);
	else if (expr->id == ids::identifier)
	{
		resolver resolver(value_table(), type_table());
		//parseme_ptr perhaps = resolver.value(expr);
	}
}

size_t calculate_parameter_type_id(parseme_ptr_ref n)
{
	if (n->id == ids::int_literal)
		return ids::int_type;
	else // if (n->id == ids::identifier)
		return ids::identifier;
}

void semantic_analysis_pass::function_call(const_parseme_ptr_ref call)
{
	// determine compatibility here
	parseme_ptr call_name = marshall::function_call::name(call);
	parseme_ptr call_PL = marshall::function_call::parameter_list(call);
	
	// get possible functions
	symbol_table::result_type results = value_table().lookup(call_name->value.string);
	
	resolver resolver(value_table(), type_table());
	
	// begin argument-dependant lookup
	parseme_container::iterator op = call_PL->children.begin();
	for ( ; op != call_PL->children.end(); ++op)
	{
		if ((*op)->id == ids::identifier)
		{
			//parseme_ptr perhaps = resolver.value(*op);
		}
	}
	
	// for each function in our list
	symbol_table::result_type::iterator candidate = results.begin();
	for (; candidate != results.end(); ++candidate)
	{
		parseme_ptr_ref candidate_PL = marshall::function::parameter_list(*candidate);
		
		if (candidate_PL->children.size() != call_PL->children.size())
			continue;
		
		// argument-dependant lookup!
		bool match = true;
		parseme_container::iterator cp = candidate_PL->children.begin();
		parseme_container::iterator op = call_PL->children.begin();
		for ( ;; ++cp, ++op)
		{
			if (cp == candidate_PL->children.end()) break;
			if (op == call_PL->children.end()) break;
			
			parseme_ptr_ref cp_type = marshall::parameter::type(*cp);
			// deduce our type from what it is
			size_t our_type = calculate_parameter_type_id(*op);
			
			//parseme_ptr_ref op_type = marshall::parameter::type(*op);
			
			if ( cp_type->id != our_type )
			{
				match = false;
				break;
			}
			else if ( cp_type->id == ids::identifier )
			{
				// user-defined types check here. hmm, scoping?
			}
		}
		
		if (match)
			break;
	}
	
	
	if (candidate == results.end())
	{
		std::cout
			<< "Error: No match function for \""
			<< call_name->value.string
			<< "("; // << "'" << std::endl;
		
		for (parseme_container::iterator i = call_PL->children.begin(); i != call_PL->children.end(); ++i)
		{
			if (i != call_PL->children.begin())
				std::cout
					<< ", ";
					
			//std::cout
			//	<< undecorated_type( marshall::argument::type(*i) );
			if ( marshall::argument::type(*i)->id == ids::int_literal )
				std::cout
					<< "int";
		}
		
		std::cout
			<< ")\""
			<< std::endl;
	}
}
*/